/*-------------------------------------------------------------------------*\
**  STON - Strongly Typed Object Notation                                  **
**  Copyright (c) 2009, Erkki Lindpere. All rights reserved.               **
**  http://code.google.com/p/ston/                                         **
**                                                                         **
**  This software is released under the terms of the Revised BSD License.  **
**  There is NO WARRANTY. See the file LICENSE for the full text.          **
\*-------------------------------------------------------------------------*/
package org.villane.ston

object ReflectiveTypes {

  private def hasSetter(clazz: Class[_], m: java.lang.reflect.Method) = try {
    clazz.getMethod(m.getName + "_$eq", m.getReturnType)
    true
  } catch {
    case e: Exception => false
  }

}

/**
 * Extends STONParser with support for reflection-based types.
 * 
 * <strong>NB!</strong> ReflectiveTypes should be mixed in before any other type-defining traits!
 */
trait ReflectiveTypes { self: STONParser =>
  private val clazzToType = collection.mutable.Map[Class[_], Type[_]]()

  def registerType[T](clazz: Class[T], typ: Type[T]) = {
    clazzToType += clazz -> typ
    // TODO this doesn't work, they are all BoxedArrays
    clazzToType += classOf[Array[T]] -> ArrayType(typ)
  }

  def getProperties(clazz: Class[_], ignored: Array[String]): Array[Property[_]] =
    for (m <- clazz.getMethods
         if m.getParameterTypes.length == 0;
         if !(Array("$tag","getClass","hashCode") ++ ignored).contains(m.getName);
         if ReflectiveTypes.hasSetter(clazz, m)
    ) yield {
      // TODO it is pointless to compute the defaults if it's just going to get set to what it is already
      PropertyWithDefault(m.getName, clazzToType(m.getReturnType), m.invoke(clazz.newInstance))
    }
 
  class ReflectiveComplexType[T](clazz: Class[T], alias: Option[String], ignoredProperties: Array[String]) extends ComplexType(
    clazz,
    getProperties(clazz, ignoredProperties)
  ) {
    override def name = alias match {
      case Some(str) => str
      case None => clazz.getName
    }
    def defaultValue = clazz.newInstance
    def create(map: collection.Map[String,Any]) = {
      val i = clazz.newInstance
      for ((name, value) <- map) {
        val meth = name + "_$eq"
        value match {
          case v: Byte =>
            clazz.getMethod(meth, java.lang.Byte.TYPE).invoke(i, java.lang.Byte.valueOf(v))
          case v: Short =>
            clazz.getMethod(meth, java.lang.Short.TYPE).invoke(i, java.lang.Short.valueOf(v))
          case v: Int =>
            clazz.getMethod(meth, java.lang.Integer.TYPE).invoke(i, java.lang.Integer.valueOf(v))
          case v: Long =>
            clazz.getMethod(meth, java.lang.Long.TYPE).invoke(i, java.lang.Long.valueOf(v))
          case v: Float =>
            clazz.getMethod(meth, java.lang.Float.TYPE).invoke(i, java.lang.Float.valueOf(v))
          case v: Double =>
            clazz.getMethod(meth, java.lang.Double.TYPE).invoke(i, java.lang.Double.valueOf(v))
          case v: Boolean =>
            clazz.getMethod(meth, java.lang.Boolean.TYPE).invoke(i, java.lang.Boolean.valueOf(v))
          case v: Char =>
            clazz.getMethod(meth, java.lang.Character.TYPE).invoke(i, java.lang.Character.valueOf(v))
          case v: AnyRef =>
            clazz.getMethod(meth, v.getClass).invoke(i, v)
        }
      }
      i
    }
  }
    

}
